package git.soulbgm.job;

import git.soulbgm.common.enums.Status;
import git.soulbgm.config.SysConfig;
import git.soulbgm.pojo.*;
import git.soulbgm.pojo.NetworkEquipmentPortInfo;
import git.soulbgm.pojo.NetworkEquipmentStatus;
import git.soulbgm.service.AlarmInfoReport;
import git.soulbgm.service.NetworkEquipmentInfoService;
import git.soulbgm.service.NetworkEquipmentPortInfoService;
import git.soulbgm.service.NetworkEquipmentStatusService;
import git.soulbgm.thread.ThreadTool;
import git.soulbgm.utils.CmdUtil;
import git.soulbgm.utils.LogHelper;
import git.soulbgm.utils.StringUtil;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 网络设备端口监测任务
 *
 * @author SoulBGM
 * @date 2022/11/25
 */
@Service
public class NetworkEquipmentMonitorTask {

    private static final LogHelper LOG = new LogHelper(NetworkEquipmentMonitorTask.class);
    private final NetworkEquipmentInfoService infoService;
    private final NetworkEquipmentPortInfoService portInfoService;
    private final NetworkEquipmentStatusService statusService;
    private final AlarmInfoReport alarmInfoReport;
    private final Integer currentRegionCode;

    public NetworkEquipmentMonitorTask(NetworkEquipmentInfoService infoService, NetworkEquipmentPortInfoService portInfoService, NetworkEquipmentStatusService statusService, AlarmInfoReport alarmInfoReport, SysConfig config) {
        this.infoService = infoService;
        this.portInfoService = portInfoService;
        this.statusService = statusService;
        this.alarmInfoReport = alarmInfoReport;
        this.currentRegionCode = config.getRegionCode();
    }

    @Scheduled(cron = "${jobs.map.networkEquipmentMonitoring.cron}")
    @SchedulerLock(name = "${jobs.map.networkEquipmentMonitoring.name}", lockAtMostFor = "${jobs.map.networkEquipmentMonitoring.lockAtMostFor}", lockAtLeastFor = "${jobs.map.networkEquipmentMonitoring.lockAtLeastFor}")
    public void execute() throws Exception {
        Date collectTime = new Date();
        Map<Long, NetworkEquipmentPortInfo> map = portInfoService.getCacheEquipmentPortInfoAll().stream()
                .filter(o -> Status.NORMAL.code.equals(o.getStatus())).collect(Collectors.toMap(NetworkEquipmentPortInfo::getId, Function.identity()));
        if (map.isEmpty()) {
            return;
        }
        List<NetworkEquipmentStatus> statusList = map.values().stream()
                .map(o -> getNetworkEquipmentStatus(o, collectTime)).collect(Collectors.toList());
        int size = map.size();
        CountDownLatch latch = new CountDownLatch(size);
        for (NetworkEquipmentStatus equipmentStatus : statusList) {
            ThreadTool.IO_INTENSIVE_THREAD_POOL.execute(new PingThread(equipmentStatus, latch));
        }

        boolean isExecuteComplete = latch.await(5L * size, TimeUnit.SECONDS);
        if (!isExecuteComplete) {
            LOG.error("执行Ping监测时间超时");
        }

        for (NetworkEquipmentStatus equipmentStatus : statusList) {
            NetworkEquipmentPortInfo info = map.get(equipmentStatus.getId());
            recordAlarm(equipmentStatus, info);
        }

        statusService.reset(statusList);
    }

    /**
     * 网络设备接口信息转换网络设备状态信息
     *
     * @param info          网络设备接口信息
     * @param detectionTime 检测时间
     * @return {@link NetworkEquipmentStatus}
     */
    public NetworkEquipmentStatus getNetworkEquipmentStatus(NetworkEquipmentPortInfo info, Date detectionTime) {
        NetworkEquipmentStatus status = new NetworkEquipmentStatus();
        status.setId(info.getId());
        status.setEquipmentId(info.getEquipmentId());
        status.setPortName(info.getPortName());
        status.setPortAlias(info.getPortAlias());
        status.setPortIp(info.getPortIp());
        status.setDetectionTime(detectionTime);
        status.setRegionCode(currentRegionCode);
        status.setStatus(CmdUtil.PING_STATUS_FAILURE);
        return status;
    }

    /**
     * 记录报警
     *
     * @param equipmentStatus 网络设备状态
     */
    public void recordAlarm(NetworkEquipmentStatus equipmentStatus, NetworkEquipmentPortInfo info) {
        Integer status = equipmentStatus.getStatus();
        if (CmdUtil.PING_STATUS_FAILURE == status) {
            // ping不通
            Long equipmentId = equipmentStatus.getEquipmentId();
            NetworkEquipmentInfo equipmentInfo = infoService.getById(equipmentId);
            String alarmContent = StringUtil.format("网络设备 {} 的编号为 {} 的 {}(别名:{}) 接口ping不通,对应IP为 {}", equipmentInfo.getEquipmentName(), info.getPortCode(), info.getPortName(), info.getPortAlias(), equipmentStatus.getPortIp());
            LOG.warn(alarmContent);
            alarmInfoReport.equipmentPingFailureAlarm(equipmentStatus.getPortIp(), alarmContent);
        } else {
            // 可以ping通
            alarmInfoReport.equipmentPingSuccessClearAlarm(equipmentStatus.getPortIp());
        }
    }

}

/**
 * ping的线程
 *
 * @author SoulBGM
 * @date 2022/11/25 18:56
 */
class PingThread implements Runnable {

    private final NetworkEquipmentStatus networkEquipmentStatus;
    private final CountDownLatch latch;

    public PingThread(NetworkEquipmentStatus networkEquipmentStatus, CountDownLatch latch) {
        this.networkEquipmentStatus = networkEquipmentStatus;
        this.latch = latch;
    }

    @Override
    public void run() {
        String ip = networkEquipmentStatus.getPortIp();
        int status = CmdUtil.ping(ip);
        networkEquipmentStatus.setStatus(status);
        latch.countDown();
    }

}